﻿using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Data;
using DevExpress.ExpressApp.DC;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Xml.Linq;

namespace EasyXaf.LowCode.WorkflowEditors.Models;

[DomainComponent]
public abstract class FlowObject : IFlowObject
{
    private readonly ConcurrentDictionary<string, object> PropertyStore = new();

    private int _lockUpdate;
    private ITypeInfo _typeInfo;

    [Key]
    [Browsable(false)]
    public string Id { get; private set; }

    [Browsable(false)]
    public bool IsUpdateLocked => _lockUpdate > 0;

    [Browsable(false)]
    public ITypeInfo TypeInfo
    {
        get => _typeInfo ??= XafTypesInfo.Instance.FindTypeInfo(GetType());
    }

    [Browsable(false)]
    public IEnumerable<ValidationResult> ValidationResults { get; protected set; }

    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    public event EventHandler ObjectChanged;

    public FlowObject()
    {
        Id = Guid.NewGuid().ToString();
        ValidationResults = new List<ValidationResult>();
    }

    private static bool CanSkipAssignment(object oldValue, object newValue)
    {
        if (oldValue == newValue)
        {
            return true;
        }

        if (oldValue is ValueType && newValue is ValueType && Equals(oldValue, newValue))
        {
            return true;
        }

        if (oldValue is string && newValue is string && Equals(oldValue, newValue))
        {
            return true;
        }

        return false;
    }

    protected T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
    {
        if (PropertyStore.TryGetValue(propertyName, out var value))
        {
            return (T)value;
        }
        return default;
    }

    protected bool SetPropertyValue<T>(T newValue, [CallerMemberName] string propertyName = null)
    {
        var oldValue = GetPropertyValue<T>(propertyName);
        if (CanSkipAssignment(oldValue, newValue))
        {
            return false;
        }

        OnPropertyChanging(propertyName);

        if (newValue == null)
        {
            PropertyStore.TryRemove(propertyName, out _);
        }
        else
        {
            PropertyStore[propertyName] = newValue;
        }

        OnPropertyChanged(propertyName);
        OnObjectChanged();

        return true;
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnObjectChanged()
    {
        if (!IsUpdateLocked)
        {
            ObjectChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    private static T ConvertValue<T>(XElement value)
    {
        var type = typeof(T);

        if (type == typeof(string))
        {
            return (T)Convert.ChangeType((string)value, type);
        }
        else if (type == typeof(bool))
        {
            return (T)Convert.ChangeType((bool)value, type);
        }
        else if (type == typeof(bool?))
        {
            return (T)Convert.ChangeType((bool?)value, type);
        }
        else if (type == typeof(int))
        {
            return (T)Convert.ChangeType((int)value, type);
        }
        else if (type == typeof(int?))
        {
            return (T)Convert.ChangeType((int?)value, type);
        }
        else if (type == typeof(uint))
        {
            return (T)Convert.ChangeType((uint)value, type);
        }
        else if (type == typeof(uint?))
        {
            return (T)Convert.ChangeType((uint?)value, type);
        }
        else if (type == typeof(long))
        {
            return (T)Convert.ChangeType((long)value, type);
        }
        else if (type == typeof(long?))
        {
            return (T)Convert.ChangeType((long?)value, type);
        }
        else if (type == typeof(ulong))
        {
            return (T)Convert.ChangeType((ulong)value, type);
        }
        else if (type == typeof(ulong?))
        {
            return (T)Convert.ChangeType((ulong?)value, type);
        }
        else if (type == typeof(float))
        {
            return (T)Convert.ChangeType((float)value, type);
        }
        else if (type == typeof(float?))
        {
            return (T)Convert.ChangeType((float?)value, type);
        }
        else if (type == typeof(double))
        {
            return (T)Convert.ChangeType((double)value, type);
        }
        else if (type == typeof(double?))
        {
            return (T)Convert.ChangeType((double?)value, type);
        }
        else if (type == typeof(decimal))
        {
            return (T)Convert.ChangeType((decimal)value, type);
        }
        else if (type == typeof(decimal?))
        {
            return (T)Convert.ChangeType((decimal?)value, type);
        }
        else if (type == typeof(DateTime))
        {
            return (T)Convert.ChangeType((DateTime)value, type);
        }
        else if (type == typeof(DateTime?))
        {
            return (T)Convert.ChangeType((DateTime?)value, type);
        }
        else if (type == typeof(DateTimeOffset))
        {
            return (T)Convert.ChangeType((DateTimeOffset)value, type);
        }
        else if (type == typeof(DateTimeOffset?))
        {
            return (T)Convert.ChangeType((DateTimeOffset?)value, type);
        }
        else if (type == typeof(TimeSpan))
        {
            return (T)Convert.ChangeType((TimeSpan)value, type);
        }
        else if (type == typeof(TimeSpan?))
        {
            return (T)Convert.ChangeType((TimeSpan?)value, type);
        }
        else if (type == typeof(Guid))
        {
            return (T)Convert.ChangeType((Guid)value, type);
        }
        else if (type == typeof(Guid?))
        {
            return (T)Convert.ChangeType((Guid?)value, type);
        }
        else if (type.IsEnum)
        {
            return (T)Convert.ChangeType(Enum.Parse(type, value.Value), type);
        }
        else
        {
            throw new NotSupportedException($"不支持从'XElement'类型到'{typeof(T).FullName}'类型的转换");
        }
    }

    protected static void SetXmlValue<T>(XElement element, string name, T value)
    {
        if (element.Element(name) is XElement subElement)
        {
            if (value == null)
            {
                subElement.Remove();
            }
            else
            {
                subElement.SetValue(value);
            }
        }
        else if (value != null)
        {
            subElement = new XElement(name);
            subElement.SetValue(value);
            element.Add(subElement);
        }
    }

    protected static void SetCDataXmlValue(XElement element, string name, string text)
    {
        if (element.Element(name) is XElement subElement)
        {
            if (text == null)
            {
                subElement.Remove();
            }
            else
            {
                subElement.ReplaceNodes(new XCData(text));
            }
        }
        else if (text != null)
        {
            subElement = new XElement(name);
            subElement.ReplaceNodes(new XCData(text));
            element.Add(subElement);
        }
    }

    protected static T GetXmlValue<T>(XElement element, string name, T defaultValue = default)
    {
        if (element.Element(name) is XElement subElement)
        {
            return ConvertValue<T>(subElement);
        }
        return defaultValue;
    }

    public virtual void OnCreated()
    {
    }

    public virtual void OnLoaded()
    {
    }

    public virtual void OnCloned()
    {
        Id = Guid.NewGuid().ToString();
    }

    public virtual void BeginUpdate()
    {
        _lockUpdate++;
    }

    public virtual void CancelUpdate()
    {
        _lockUpdate--;
    }

    public virtual void EndUpdate()
    {
        _lockUpdate--;

        if (_lockUpdate == 0)
        {
            OnObjectChanged();
        }
    }

    public void RaiseObjectChanged()
    {
        OnObjectChanged();
    }

    public void RaisePropertyChanging(string propertyName)
    {
        OnPropertyChanging(propertyName);
    }

    public void RaisePropertyChanged(string propertyName)
    {
        OnPropertyChanged(propertyName);
    }

    public virtual object GetMemberValue(IMemberInfo memberInfo)
    {
        return memberInfo.GetValue(this);
    }

    protected virtual void SetMemberValueCore(IMemberInfo memberInfo, object value)
    {
        memberInfo.SetValue(this, value);
    }

    public virtual void SetMemberValue(IMemberInfo memberInfo, object value)
    {
        try
        {
            BeginUpdate();
            SetMemberValueCore(memberInfo, value);
        }
        finally
        {
            EndUpdate();
        }
    }

    public virtual void WriteStateToXml(XElement element)
    {
    }

    public virtual void ReadStateFromXml(XElement element)
    {
    }

    public virtual void WriteToXml(XElement element)
    {
        SetXmlValue(element, nameof(Id), Id);
        WriteStateToXml(element);
    }

    public virtual void ReadFromXml(XElement element)
    {
        Id = GetXmlValue<string>(element, nameof(Id));
        ReadStateFromXml(element);
    }

    public virtual string ToXml()
    {
        var element = new XElement(GetType().Name);
        WriteToXml(element);
        return element.ToString();
    }

    public virtual IEnumerable<ValidationResult> Validate()
    {
        return Array.Empty<ValidationResult>();
    }
}
